#include <errno.h>

#define DBG_SUBSYS S_LIBYNET

#include "ynet_net.h"
#include "ynet_rpc.h"
#include "net_global.h"
#include "fence.h"
#include "dbg.h"

typedef struct {
	worker_handler_t sem;
	sy_spinlock_t lock;
	int inited;
	time_t last_test;
} fence_t;

static fence_t fence;
static int __fence_test_need__ = 1;

static int __fence_load_host(__host_t *hosts, int *_count)
{
	int ret, count, len;
	char hostline[STR_LEN], path[MAX_PATH_LEN];
	FILE *fp;

	count = 0;

	snprintf(path, MAX_PATH_LEN, "%s/etc/cluster.conf", gloconf.home);
	fp = fopen(path, "r");
	if (fp == NULL) {
		ret = errno;
		if (ret == ENOENT) {
			goto out;
		} else {
			DWARN("fence arping open config file %s failed\n", path);
			GOTO(err_ret, ret);
		}
	}

	while (1) {
		if (fgets(hostline, STR_LEN, fp) == NULL) {
			break;
		}

		if (hostline[0] == '#')
			continue;

		len = strlen(hostline);
		if (hostline[len - 1] == '\n') {
			hostline[len - 1] = '\0';
		}

		strcpy(hosts[count].host, hostline);
		count++;
	}

	fclose(fp);
out:
	*_count = count;

	return 0;
err_ret:
	return ret;
}

static int  __get_host_info(struct list_head *hlist, int count, __host_t *hosts,
                            struct list_head *nlist, int *_hostnum)
{
	int ret, herrno, hostnum = 0, i, idx;
	struct in_addr src, dst;
	struct hostinfo *host;
	char *hostline, hostname[STR_LEN];
	char buf[MAX_BUF_LEN];
	struct hostent hostbuf, *result;

	net_gethostname(hostname, sizeof(hostname));

	for (idx = 0; idx < count; idx++) {
		hostline = hosts[idx].host;

		DBUG("ping %s\n", hostline);

		if (strcmp(hostname, hostline) == 0) {
			hostnum++;
			continue;
		}

		ret = gethostbyname_r(hostline, &hostbuf, buf, sizeof(buf),  &result, &herrno);
		if (ret < 0) {
			ret = errno;
			GOTO(err_ret, ret);
		}

                if (result == NULL) {
//                        ret = ENONET;
                        DWARN("can't found %s\n", hostline);
			continue;
//                        GOTO(err_ret, ret);
                }

		host = malloc(sizeof(struct hostinfo));
		if (host == NULL) {
			DWARN("fence arping malloc hostinfo error\n");
			return ENOMEM;
		}

		memset(host, 0x00, sizeof(struct hostinfo));

		host->nic_num = 0;
		host->cur_index = -1;
		strcpy(host->hostname, hostline);
		for(i = 0; result->h_addr_list[i] != NULL; i++) {
			char devname[16];

			dst = *(struct in_addr *)(result->h_addr_list[i]);

			host->intf[host->nic_num].nic = get_src_ip(dst, &src, nlist, devname);
			if (host->intf[host->nic_num].nic == NULL)
				continue;
			else{
				host->intf[host->nic_num].src = src;
				host->intf[host->nic_num].dst = dst;
				host->intf[host->nic_num].host = host;
				host->nic_num++;

			}

		}

		if (host->nic_num == 0) {
			DWARN("fence arping host %s get ip address failed \n ", host->hostname);
			free(host);
			continue;
		}

		int retry = 0;
ident_retry:		
		host->seq = 0;
		host->ident = ping_get_ident() & 0xffff;
		if(host->ident == -1){
			if(retry < 3){
				retry++;
				GOTO(ident_retry, errno);
			} else {
				YASSERT(0);
			}
		}
		
		hostnum++;
		host->fd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
		if(host->fd < 0){
			int ret;
			printf("socket alloc error %s\n", strerror(errno));			
			free(host);
			ret = errno;

			return ret;
		}

		list_add_tail(&host->list, hlist);

	}
	
	*_hostnum = hostnum;

	return 0;

err_ret:
	return ret;
}

static int  __icmp_request(struct list_head *hlist, struct list_head *nlist, int total)
{
	unsigned int ret ;
	int tim = 1, ok = 0;

retry:
	ok = handle_icmp_packs(hlist, total);

	DBUG("fence test %u/%u\n", ok, total);
#if 0
	if (ok < (total / 2 + 1) && tim++ < 3)
		goto retry;
	else if (tim >= 3) {
		ret = ENONET;
		GOTO(err_ret, ret);
	}
#else
	
	if (ok < (total / 2 + 1)) {
		if (tim++ < 3) {
			goto retry;
		} else {
            DWARN("fence test %u/%u\n", ok, total);
			ret = ENONET;
			GOTO(err_ret, ret);
		}
	}
#endif
	del_all_host(hlist);
	del_all_nic(nlist);

	return 0;

err_ret:

	return ret;
}


static int __run(void)
{
	int ret, hostnum = 0;
	int  count = 0;
	struct list_head hlist, nlist;
	__host_t hosts[1024];

	INIT_LIST_HEAD(&hlist);
	INIT_LIST_HEAD(&nlist);

	ret = get_nic_info(&nlist);
	if (unlikely(ret)) {
		DWARN("fence arping get nic info failed in this host\n");
		ret = ENONET;
		GOTO(err_ret, ret);
	}

	ret = __fence_load_host(hosts, &count);
	if (unlikely(ret))
		GOTO(err_ret1, ret);

	if (count == 0) {
		goto out;
	}

	ret = __get_host_info(&hlist, count, hosts, &nlist, &hostnum);
	if(ret)
		GOTO(err_ret, ret);

	if (hostnum <= count / 2) {
		ret =  ENONET;
		GOTO(err_ret, ret);
	}

	ret = __icmp_request(&hlist, &nlist, count);
	if (unlikely(ret))
		GOTO(err_ret, ret);

out:
	return 0;
err_ret:
	del_all_host(&hlist);
err_ret1:
	del_all_nic(&nlist);
	return ret;
}


int fence_test_sync()
{
	int ret;

	if (__fence_test_need__ == 0)
		return 0;

	ret = __run();
	if (unlikely(ret)) {
                GOTO(err_ret, ret);
	}

	return 0;
err_ret:
	return ret;
}



static int __fence_worker(void *arg)
{
	int ret;

	(void) arg;

	DBUG("fence test begin\n");

        ANALYSIS_BEGIN(0);

	ret = fence_test_sync();
	if (unlikely(ret)) {
                DWARN("fence_test fail\n");
                SWARN(0, "%s fence_test fail\n", M_FUSIONSTOR_FENCE_WARN);
                EXIT(ret);
	}

        ANALYSIS_END(0, 3 * 1000 * 1000, NULL);

	DBUG("fence test finished\n");

	return 0;
}


int fence_init(const char *home)
{
	int ret;

        DINFO("fence init\n");
	(void) home;

	fence.last_test = 0;

	ret = sy_spin_init(&fence.lock);
	if (unlikely(ret))
		GOTO(err_ret, ret);

	fence.inited = 1;

	ret = worker_create(&fence.sem, "fence", __fence_worker,
                            NULL, NULL, WORKER_TYPE_SEM, 0);
	if (unlikely(ret))
		GOTO(err_ret, ret);

	return 0;
err_ret:
	return ret;
}

int fence_test()
{
	int ret;

	if (fence.inited == 0)
		return 0;

	if (__fence_test_need__ == 0)
		return 0;

	ret = sy_spin_lock(&fence.lock);
	if (unlikely(ret))
		GOTO(err_ret, ret);

	if ((int)(gettime() - fence.last_test) < 5) {
		sy_spin_unlock(&fence.lock);
		goto out;
	}

	DBUG("diff %d\n", (int)(gettime() - fence.last_test));

	fence.last_test = gettime();

	sy_spin_unlock(&fence.lock);

	worker_post(&fence.sem);

out:
	return 0;
err_ret:
	return ret;
}

